/*
 * Copyright (c) 2003, 2004, 2005, 2006 Israfil Consulting Services Corporation
 * Copyright (c) 2003, 2004, 2005, 2006 Christian Edward Gruber
 * All Rights Reserved
 * 
 * This software is licensed under the Berkeley Standard Distribution license,
 * (BSD license), as defined below:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Israfil Consulting Services nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 * 
 * $Id: NotificationCentre.java 123 2006-11-09 15:49:23Z cgruber $
 */
package net.israfil.foundation.notification;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import net.israfil.foundation.core.DynamicUtil;

/**
 * @author cgruber
 */
public class NotificationCentre {
    
    private static NotificationCentre _shared = new NotificationCentre();

    private Set observers = new HashSet();
    
    protected NotificationCentre() {
    }

    public static NotificationCentre defaultCentre() {
        return _shared;
    }

    /**
     * This registers an object as an observer of notifications.  The object 
     * registers interest in either a particular named notification, or a 
     * particular object who may issue the notifications, or the union of the 
     * two.  The following table lists the possible combinations:
     * <table>
     *   <tr>
     *     <th>Notification Name</th>
     *     <th>Sender Object</th>
     *     <th>Effect</th>
     *   </tr>
     *   <tr>
     *     <td>null</td>
     *     <td>a sender</td>
     *     <th>Notifications (of any name) from this sender are observed</th>
     *   </tr>
     *   <tr>
     *     <td>notification string</td>
     *     <td>null</td>
     *     <th>Notifications with this name (from any sender) are observed.</th>
     *   </tr>
     *   <tr>
     *     <td>notification string</td>
     *     <td>a sender</td>
     *     <th>Only notifications with this name and originating from this sender are observed</th>
     *   </tr>
     *   <tr>
     *     <td>null</td>
     *     <td>null</td>
     *     <th>All Notifications from any source will be sent (big performance drag)</th>
     *   </tr>
     * </table>
     * @param observer The object instance that wants to be registered
     * @param callbackSignature Must be a method that takes a single Object parameter which will be a Notification at run-time, or a single Notification parameter
     * @param notification The name of the notification to watch for (if any)
     * @param sender The name of the object to whose notifications should be paid attention. (if any)
     */
    public void addObserver(Object observer, 
                            String callbackSignature,
                            String notification,
                            Object sender){ 
        if (observer == null) return;
        if (callbackSignature == null) 
            throw new IllegalArgumentException("Reciever did not pass in a callback method.");
        if (!checkSignature(observer,callbackSignature))
            throw new IllegalArgumentException("Reciever does not implement specified callback method.");
        observers.add(new ObservationSpec(sender,notification, callbackSignature, observer));
        
    }
    /** Post a notification with the given nam and notifier. */
    public void postNotification(Notification notification) {
    	for (Iterator i = this.observers.iterator(); i.hasNext();) {
    		((ObservationSpec)i.next()).postNotification(notification);
    	}
    }
    /** Post a notification with the given name, notifier, and with additional 
     *  context information presented in a java.util.Map. */
    public void postNotification(Object sender,String notification) {
        postNotification(new Notification(sender,notification));
    }
    /** Post a notification with the given name, notifier, and with additional 
     *  context information presented in a java.util.Map. */
    public void postNotification(Object sender,String notification, Map info) {
        postNotification(new Notification(sender,notification,info));
    }
    /** Removes an observer from any notofications with either or both a 
     * provided notification string, or notifier
     */
    public void removeObserver(Object observer, String notification, Object sender) {
		for (Iterator i = observers.iterator(); i.hasNext();) {
			ObservationSpec obs = (ObservationSpec) i.next();
			if (obs.observer == observer &&
				obs.sender == sender &&
				(obs.notification == null || obs.notification.equals(notification))){
				observers.remove(obs);	
			}
		}
    }
    public void clearObservers() {
    	observers.clear();
    }
    
    private boolean checkSignature(Object receiver,String selector) {
        if (!DynamicUtil.respondsTo(receiver,selector)) return false;
        StringTokenizer st = new StringTokenizer(selector,":");
        if (st.countTokens() != 2) return false;
        st.nextToken();
        String parameter = st.nextToken();
        if (Notification.class.getName().equals(parameter)) return true;
        if (Object.class.getName().equals(parameter)) return true; 
        return false;
    }
    
    /**
     * TODO Use WeakReferences
     */
    public class ObservationSpec {
        public final Object sender;
        public final String notification;
        public final String callback;
		public final Object observer;
        private final int _hashCode;
        public ObservationSpec(Object sender,String notification,String callback, Object observer) {
            if (observer == null)
                throw new IllegalArgumentException("Both sender and notification cannot be null");
            this.sender = sender;
            this.notification = notification;
			this.callback = callback;
			this.observer = observer;
			
			// Pre-calculate hash
            int code = 13;
            if (sender != null) code *= sender.hashCode();
            if (notification != null) code *= notification.hashCode();
			code *= observer.hashCode();
            this._hashCode = code;
        }
        public void postNotification(Notification notification) {
			if (this.sender == null && this.notification == null) {
				    _post(notification);				
			} else if (this.sender == null) {
        		if (notification.notification.equals(this.notification))
					_post(notification);				
        	} else if (this.notification == null) {
				if (notification.sender.equals(this.sender))
					_post(notification);				
        	} else {
				if (notification.notification.equals(this.notification) && notification.sender.equals(this.sender))
					_post(notification);				
        	}
        }
		private void _post(Notification notification) {
			DynamicUtil.performOn(this.observer,callback,new Object[]{notification} );
		}
		
		//@Override
        public int hashCode() {
            return _hashCode;
        }
    }
    
}
